iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 2
0
自我挑戰組

競技程式設計的藝術系列 第 3

存資料 (゚д゚)

  • 分享至 

  • xImage
  •  

各位抱歉,由於最近筆者時間分配不佳,導致文章來不及在鐵人賽第三天達成
不過如果我有空閒,一樣會繼續寫作

資料結構

簡單說,它是一種對資料的有系統的整理,
通常一個資料結構的誕生是為了另在其之上運作的演算法,能更好的進行操作,或是為了提昇演算法的效率,甚至是為了方便解釋演算法的運作(抽象化)。
每種資料結構一般都是針對:新增,刪除,修改,查詢 這些功能的效率及操作上的追求。

Queue

要搭公車之前,都是先排隊進入公車站,等到公車到來時,在從公車站出去搭上公車;
由此,公車站可以作為一種資料結構,人可類比為欲儲存之資料
source
此種資料結構稱作隊列 (queue);擁有先進先出 (first in, first out) 的特性,
例如在郵局要辦事,會先從機器拿號碼紙等叫號,這時候就是用 Queue 去儲存編號。
下面給出隊列的簡易實作程式碼:

int Q[maxn], front_idx = 0, back_idx = 0;

void enqueue(int data)
  { Q[back_idx++] = data; }

int front()
  { return Q[front_idx]; }
  
bool empty()
  { return front_idx == back_idx; }

void dequeue()
  { if(!empty()) front_idx++; }

當我們操作多次 enqueuedequeue,會使得 front_idx 最終會碰到陣列邊界,這樣會導致能儲存的資料量變小。

觀察發現,當 front_idx 增加,相當於丟掉以前的資料,這時候就有舊的空間空出來了,該如何使用這塊舊空間呢?
直接的,讓 back_idx 碰到邊界後回去初始位置就可以了: 這種資料結構稱作環狀隊列

還有種變種隊列,叫做雙端隊列(Double ended queue),他可以從前面或後面 enqueuedequeue

Stack

考慮將一堆鬆餅每次只放一片疊到盤子上,則最後放上去的鬆餅將會是最上層的鬆餅,如果要吃也會依序從最上層開始拿。
source

此種鬆餅(資料)的放法,拿法,是種稱做堆疊 (stack) 的資料結構;有後進先出 (last in, first out) 的特性,
下面給出隊列的簡易實作程式碼:

int S[maxn], top_idx = -1;

void push(int data)
  { S[++top_idx] = data; }

int top()
  { return S[top_idx]; }
  
bool empty()
  { return top_idx == -1; }

void pop()
  { if(!empty()) top_idx--; }

Linked list

玩某些撲克牌遊戲時常常會有要在手中整理牌的動作,將一張牌拿起與將一張牌插到某個位置,支援這兩個行為的資料結構稱為連結串列 (Linked list)
source

連結串列比較複雜點,我們需要造出兩種結構:

  1. 我們要定義一個結構叫做節點(node),它可儲存資料及下個節點的位置
struct node {
  int data;
  node *prev, *next;
} *list[maxn];
  1. 當資料都放進節點後,要將節點們串起來
    其中 list[0] 不當一般資料使用,它用來指向連接串列的頭
for (int i = 0; i < N; i++) {
  node *p = new node;

  if(i) {
    list[i-1]->next = list[i] = p;
    list[i]->prev = list[i-1];
  } else list[0] = p;
}
  
list[0]->perv = list[N-1];
list[N-1]->next = list[0];

在最後我將 list[0](head_pointer) 與串列尾部連接起來,是為了下面兩個函式寫法上的方便(?

當擁有這樣的結構,拔除節點和插入節點只需要 O(1):

void remove(int idx) {
  list[idx]->prev->next = list[idx]->next;
  list[idx]->next->prev = list[idx]->prev;
  list[idx]->next = list[idx]->prev = NULL;
}

void insert(int idx1, int idx2) {
  list[idx2]->next = list[idx1]->next;
  list[idx1]->next = list[idx2];
  list[idx2]->next->prev = list[idx2];
  list[idx2]->prev = list[idx1];

練習:
Sprout OJ 21 陸行鳥大賽車

Graph

圖 (Graph),是一個由邊 (Edge)集合與點 (Vertex)集合所組成的資料結構。

Graph 的術語:

  • 點 (vertex): 組成圖的最基本的元素
  • 邊 (edge): 點與點的關係
  • 有向圖 (directed graph): 邊帶有方向性
  • 無向圖 (undirected graph): 每條邊都是雙向的
  • 道路 (walk): 點邊相間的序列,https://chart.googleapis.com/chart?cht=tx&amp;chl=v_0e_1v_1e_2v_2..e_nv_n
  • 路徑 (path): 點不重複的道路
  • 環 (cycle): 路徑的起點與終點連接後形成環
  • 入度 (in-degree): 連到(具方向)該點邊的數量
  • 出度 (out-degree): 該點往外連的邊的數量
  • 走訪/邊歷 (traversal/search): 走完全部的點或邊
  • 連通 (connected): 任兩點至少有一條路徑

在討論圖的邊,常會有 u 是邊起點與 v 是邊終點的慣例用符
https://ithelp.ithome.com.tw/upload/images/20190212/20107376AnMEyUPNL8.png

通常 graph 用鄰接表 (adjacency list)或鄰接矩陣 (adjacency matrix)儲存資料

  • 鄰接表
struct edge {
  int u, v, w; //兩個相鄰點與邊權重
  edge(int a, int b, int c): u(a), v(b), w(c){}
};

vector<edge> graph;

int main() {
    :
    .
  for (int i = 1; i <= n; i++) {
    scanf("%d%d%d", &u, &v, &w);
    graph.push_back(edge(u, v, w));
  }
}
  • 鄰接矩陣
int graph[MAXN][MAXN];

int main() {
    :
    .
  for (int i = 1; i <= n; i++) {
    scanf("%d%d%d", &u, &v, &w);
    graph[u][v] = w;
  }
}

Tree

樹 (Tree),這個資料結構在圖像化看起來像顆倒掛的,根在上,而葉子在下。

Tree 的術語及特點:

  • Tree 是一個有向無環連通圖 (與圖論中的 Tree 不同,為無向圖)
  • 節點 (node): 一般樹上的點不使用圖的術語 vertex
  • 父 (parent): 節點能反向拜訪的第一個節點
  • 子 (child): 節點能正向拜訪的第一個節點
  • 祖先 (ancestor): 節點能反向拜訪的所有節點
  • 孫子 (descendant): 節點能正向拜訪的所有節點
  • 根 (root): 沒有父節點的節點
  • 葉 (leaf): 沒有子節點的節點
  • 深度 (depth): 節點的深度為從根到該節點所經過的邊數
  • 森林 (forest): 一個集合包含所有不相交的 Tree
  • 每個非根節點只有一個父節點

Disjoint sets

中文稱互斥集(複數),是指兩兩集合之間並沒有相同元素的一群集合。
常用來處裡"分類"問題;實作此資料結構我們可以稱作併查森林 (Union-Find Forest)

一般在做集合操作,最直觀的想法就是將每個集合有哪些元素,都用 Array, Vector, Binary Search tree 或 List 紀錄起來
而一般常見的集合操作有,新增、刪除、(取)聯集、取交集、取集合大小,可以稍微思考一下這些操作的複雜度要多少。

但併查森林則是將紀錄方式從 "每個集合有哪些元素" 改為 "每個元素屬於哪個集合" (這操作太神啦

Initialization

void disjoin_set_init() {
  for (int v = 1; v <= N; v++) group[v] = v;
}

https://ithelp.ithome.com.tw/upload/images/20181023/20107376JUYnAzqz1N.png

Find

int Find(int v) {
  if (v == group[v]) return v;
  return group[v] = Find(group[v]); //Path Compression
}

假設有 1 ~ 5 元素,其中 1、2 一組,3、4、5 一組。
https://ithelp.ithome.com.tw/upload/images/20181023/201073761FL6lehqWH.png
我下 Find(5) 指令,那麼他要回傳給我 3
group[v] = Find(group[v]); 因為在子節點回溯時順便把最上層 group 的標號(也就是 3)給了所有有拜訪過的節點,
於是 disjoint set 的關係圖變成:
https://ithelp.ithome.com.tw/upload/images/20181023/201073769wxYixtkAy.png

Union

void Union(int u, int v) {
  group[Find(u)] = Find(v);
}

若對下圖這樣的情況,我做 Union(4, 2);

https://ithelp.ithome.com.tw/upload/images/20181023/201073769wxYixtkAy.png
則上圖會變為下圖這樣:
https://ithelp.ithome.com.tw/upload/images/20181023/20107376WWL1SSRkke.png
還有種方式稱作 Union by rank,將 rank 大的樹合併到 rank 小的樹下,可以加快許多。

練習:
UVa OJ 10583 Ubiquitous Religions
UVa OJ 11987 Almost Union-Find


稍微抱怨一下,IT邦為什麼沒有 latex 可以用QQ
而且超連結貌似含一些特殊字元就會失效QQ

希望之後能少耍點廢,盡量產文章和打比賽
老實說覺得這樣一篇的內容好像有點多,是不是應該拆成幾份談會比較好啊(
在之後未來某篇應該會介紹更高級的資料結構,例如 heap, RMQ, BIT, treap 之類的
如果內容有錯誤,請大家幫個忙指正一下!
那麼感謝大家觀看,咱們改天見!


上一篇
效率與思維_(:3 」∠ )_
下一篇
搜尋 #1 (,,・ω・,,)
系列文
競技程式設計的藝術8
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
卡卡恩
iT邦新手 4 級 ‧ 2018-10-23 06:53:55

編輯框上面好像有個「加入數學公式」,雖然還是超級不方便但可以將就一下XD

https://ithelp.ithome.com.tw/upload/images/20181023/20112376JmUuthaBPJ.png

YS iT邦新手 5 級 ‧ 2018-10-23 15:57:00 檢舉

哦哦哦,這已經比打好截圖在貼上好很多了d(`・∀・)b

我要留言

立即登入留言